using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Phoenix.Diagnostics;
using Phoenix.Framework;
using Phoenix.Lifetime;
using Phoenix.Storage;

namespace Phoenix.Frames
{
    public class FrameJournal : IFrameJournal
    {
        readonly string frameName;
        internal const string JournalKeyPattern = "NavigationJournalFor{0}";
        readonly List<NavigationRequest> journal;

        public FrameJournal(string frameName)
        {
            this.frameName = frameName;
            journal = new List<NavigationRequest>();
        }

        public void Initialise(ITransientStore transientStore)
        {
            if (!transientStore.Contains(JournalKey)) return;

            var history = transientStore.Load<JournalNavigationRequest[]>(JournalKey);
            if (history != null)
            {
                Trace.WriteDebug(this, () => string.Format("Journal loaded {0} requests from Isolated Storage", history.Length));
                var navigationRequests = history
                    .Select(h =>
                                {
                                    var controllerActionParameters = h.Parameters != null ?
                                        h.Parameters.Select(p => new ControllerActionParameter(p.Name, p.Value)) :
                                        null;
                                    var controllerAction = ControllerAction.CreateFrom(h.Command);
                                    var requestTransientStore = new RequestTransientStore(h.TransientState);
                                    return new NavigationRequest(controllerAction, controllerActionParameters, null, requestTransientStore, h.Options);
                                });
                journal.AddRange(navigationRequests);
            }
        }

        private string JournalKey
        {
            get { return string.Format(JournalKeyPattern, frameName); }
        }

        public void Save(ITransientStore transientStore)
        {
            if (transientStore.Contains(JournalKey))
                transientStore.Remove(JournalKey);

            Trace.WriteDebug(this, () => string.Format("Journal saved {0} requests to Isolated Storage", journal.Count));
            var journalNavigationRequests = CreateJournalEntries(journal);
            transientStore.Save(JournalKey, journalNavigationRequests);
        }

        static JournalNavigationRequest[] CreateJournalEntries(IEnumerable<NavigationRequest> navigationRequests)
        {
            return navigationRequests
                .Select(r =>
                            {
                                var requestTransientStore = r.RequestTransientStore as RequestTransientStore;

                                var parameters = r.Parameters != null
                                                     ? r.Parameters
                                                           .Select(p => new JournalControllerActionParameter
                                                                            {
                                                                                Name = p.Name,
                                                                                Value = p.Value
                                                                            })
                                                           .ToArray()
                                                     : null;
                                return new JournalNavigationRequest
                                           {
                                               Command = r.ControllerAction.ToString(),
                                               Parameters = parameters,
                                               TransientState = requestTransientStore == null ? null : requestTransientStore.State
                                           };
                            })
                .ToArray();
        }

        public void Add(NavigationRequest navigationRequest)
        {
            journal.Add(navigationRequest);
        }

        public NavigationRequest Pop()
        {
            var last = journal.LastOrDefault();
            if (last!=null)
                journal.Remove(last);
            return last;
        }

        public bool TrimTo(string assembly, string controller, string action)
        {
            var request = journal.SingleOrDefault(r => IsMatch(assembly, controller, action, r));
            if (request == null)
                return false;

            var index = journal.IndexOf(request);
            if (index == -1)
                return false;

            journal.RemoveRange(index, journal.Count - index);
            return true;
        }

        static bool IsMatch(string assembly, string controller, string action, NavigationRequest r)
        {
            return (string.IsNullOrEmpty(assembly) || r.ControllerAction.Assembly == assembly) &&
                   r.ControllerAction.Controller == controller &&
                   r.ControllerAction.Action == action;
        }

        public int Count
        {
            get { return journal.Count; }
        }

        public IEnumerator<NavigationRequest> GetEnumerator()
        {
            return journal.GetEnumerator();
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }
    }
}